# jsb/runner.py
#
#

""" threads management to run jobs. """

## jsb imports

from jsb.lib.threads import getname, start_new_thread, start_bot_command
from jsb.utils.exception import handle_exception
from jsb.utils.locking import locked, lockdec
from jsb.utils.lockmanager import rlockmanager, lockmanager
from jsb.utils.generic import waitevents
from jsb.utils.trace import callstack, whichmodule
from jsb.lib.threadloop import RunnerLoop

## basic imports

import Queue
import time
import thread
import random
import logging
import sys

## Runner class

class Runner(RunnerLoop):

    """
        a runner is a thread with a queue on which jobs can be pushed. 
        jobs scheduled should not take too long since only one job can 
        be executed in a Runner at the same time.

    """

    def __init__(self, name, queue=None, priority=True):
        RunnerLoop.__init__(self, name, queue, priority)
        self.working = False
        self.nowrunning = ""

    def handle(self, speed, modname, func, *args, **kwargs):
        """ schedule a job. """
        self.working = True
        try:
            self.nowrunning = getname(func)
            logging.warn('running %s' % self.nowrunning)
            func(*args, **kwargs)
        except Exception, ex: handle_exception()
        self.working = False

    def done(self, event): pass
                            
## BotEventRunner class

class BotRunner(Runner):

    def handle(self, speed, bot, ievent):
        """ schedule a bot command. """
        try:
             logging.info("got event %s (%s) on %s bot" % (ievent.cbtype, ievent.userhost, bot.cfg.name))
             bot.doevent(ievent)
             bot.benice(sleep=0.001) 
        except Exception, ex: handle_exception()
        

class OutputRunner(Runner):

    def __init__(self, name, queue=None, priority=False):
        Runner.__init__(self, name, queue, priority)

    def handle(self, speed, bot, ievent):
        """ schedule a bot command. """
        try: bot.blaet(ievent)
        except Exception, ex: handle_exception()
        time.sleep(0.01)

class BotEventRunner(Runner):

    def handle(self, speed, *args):
        """ schedule a bot command. """
        try:
            descr, func, bot, ievent = args
            logging.info("event handler is %s" % str(func))
            self.working = True
            self.nowrunning = getname(func)
            func(bot, ievent)
            ievent.ready()
            bot.benice()
        except Exception, ex: handle_exception()
        self.working = False
        logging.info("finished  %s" % self.nowrunning)

class LongRunner(Runner):

    def handle(self, speed, *args):
        """ schedule a bot command. """
        try:
            descr, func, bot, ievent = args
            self.nowrunning = getname(func)
            logging.info("long event handler is %s" % str(func))
            self.working = True
            func(bot, ievent)
            ievent.ready()
        except Exception, ex:
            handle_exception()
        self.working = False
        logging.info("long finished - %s" % self.nowrunning)

## Runners class

class Runners(object):

    """ runners is a collection of runner objects. """

    def __init__(self, name, max=100, runnertype=Runner):
        self.name = name or "default"
        self.max = max
        self.runners = []
        self.runnertype = runnertype

    def names(self):
        return [getname(runner.name) for runner in self.runners]

    def size(self):
        qsize = [runner.queue.qsize() for runner in self.runners]
        return "%s/%s" % (qsize, len(self.runners))

    def runnersizes(self):
        """ return sizes of runner objects. """
        result = []
        for runner in self.runners: result.append("%s - %s" % (runner.queue.qsize(), runner.name))
        return result

    def stop(self):
        """ stop runners. """
        for runner in self.runners: runner.stop()

    def start(self):
        """ overload this if needed. """
        pass
 
    def put(self, speed, *data):
        """ put a job on a free runner. """
        logging.debug("size is %s" % len(self.runners))
        for runner in self.runners:
            if runner.queue.empty(): runner.put(speed, *data) ; return
        runner = self.makenew()
        runner.put(speed, *data)
        self.cleanup()
         
    def running(self):
        """ return list of running jobs. """
        return [runner.nowrunning for runner in self.runners]

    def makenew(self):
        """ create a new runner. """
        runner = None
        if len(self.runners) < self.max:
            runner = self.runnertype(self.name)
            runner.start()
            self.runners.append(runner)
        else: runner = random.choice(self.runners)
        return runner

    def cleanup(self, *args, **kwargs):
        """ clean up idle runners. """
        if not len(self.runners): logging.debug("nothing to clean")
        r = []
        for runner in self.runners:
            if runner.queue.empty() or runner.queue.qsize() == 0: runner.stop() ; r.append(runner)
        for runner in r:
            try: self.runners.remove(runner)
            except ValueError: pass
        logging.info("cleaned %s" %  str([runner.nowrunning for runner in r]))


